/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.openide.execution; import java.io.IOException; import java.io.InterruptedIOException; import java.io.Reader; import java.io.Writer; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.InputStream; import java.io.OutputStream; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.text.MessageFormat; import java.util.Enumeration; import java.util.ResourceBundle; import java.util.ArrayList; import java.util.StringTokenizer; import org.openide.TopManager; import org.openide.filesystems.*; import org.openide.execution.NbProcessDescriptor; import org.openide.util.HelpCtx; import org.openide.util.MapFormat; import org.openide.util.NbBundle; import org.openide.util.Utilities; import org.openide.util.TaskListener; import org.openide.util.Task; import org.openide.util.io.FoldingIOException; import org.openide.windows.InputOutput; import org.openide.windows.OutputWriter; /** Executes a class externally (in a separate process). Provides * basic implementation that allows to specify the process to * execute, its parameters and also to substitute the content of repositorypath, * classpath, bootclasspath and librarypath. This is done by inner class Format. * <P> * The behaviour described here can be overriden by subclasses to use different * format (extend the set of recognized tags), execute the * process with additional environment properties, etc. * * @author Ales Novak, Jaroslav Tulach */ public class ProcessExecutor extends Executor { /** generated Serialized Version UID */ static final long serialVersionUID = 1440216248312461457L; // the bundle to use static ResourceBundle bundle = NbBundle.getBundle(ProcessExecutor.class); /** default descriptor to use */ private static final NbProcessDescriptor DEFAULT = new NbProcessDescriptor ( "{" + Format.TAG_JAVAHOME + "}{" + Format.TAG_SEPARATOR + "}bin{" + Format.TAG_SEPARATOR + "}java", // /usr/local/bin/java // NOI18N "-cp {" + Format.TAG_REPOSITORY + "}" + // -cp {REPOSITORY}:{CLASSPATH} {CLASSNAME} {ARGUMENTS} // NOI18N "{" + Format.TAG_PATHSEPARATOR + "}" + "{" + Format.TAG_CLASSPATH + "}" + // NOI18N "{" + Format.TAG_PATHSEPARATOR + "}" + "{" + Format.TAG_LIBRARY + "} " + // NOI18N "{" + Format.TAG_CLASSNAME + "} " + // NOI18N "{" + Format.TAG_ARGUMENTS + '}', // NOI18N bundle.getString ("MSG_ExecutorHint") ); /** external process - like java.exe - property */ private NbProcessDescriptor externalExecutor; /** class path settings or null */ private NbClassPath classPath; /** boot class path or null */ private NbClassPath bootClassPath; /** environment vars or null */ private String[] envp = null; /** working directory or null */ private File cwd = null; /** Create a new executor. * The default Java launcher associated with this VM's installation will be used, * and the user repository entries will be used for the class path. */ public ProcessExecutor() { externalExecutor = DEFAULT; } /** Set a new external execution command. * @param desc the settings for the new external executor */ public synchronized void setExternalExecutor (NbProcessDescriptor desc) { NbProcessDescriptor old = externalExecutor; externalExecutor = desc; firePropertyChange ("externalExecutor", old, desc); // NOI18N } /* Default human-presentable name of the executor. * In the default implementation, just the class name. * @return initial value of the human-presentable name */ protected String displayName () { return bundle.getString ("ExternalExecutionDisplayName"); } public HelpCtx getHelpCtx () { return new HelpCtx (ProcessExecutor.class); } /** Get the current external execution command. * @return the settings for the current external executor */ public NbProcessDescriptor getExternalExecutor() { return externalExecutor; } /** Getter for class path associated with this executor. */ public NbClassPath getClassPath () { return classPath == null ? NbClassPath.createClassPath () : classPath; } /** Setter for class path for this executor. */ public synchronized void setClassPath (NbClassPath path) { NbClassPath old = classPath; classPath = path; firePropertyChange ("classPath", old, path); // NOI18N } /** Getter for boot class path associated with this executor. */ public NbClassPath getBootClassPath () { return bootClassPath == null ? NbClassPath.createBootClassPath () : bootClassPath; } /** Setter for boot class path for this executor. */ public synchronized void setBootClassPath (NbClassPath path) { NbClassPath old = bootClassPath; bootClassPath = path; firePropertyChange ("bootClassPath", old, path); // NOI18N } /** Getter for repository path. It is immutable reflecting * NbClassPath.createRepositoryPath (). Is here only to be displayed in property sheet. */ public NbClassPath getRepositoryPath () { return NbClassPath.createRepositoryPath (FileSystemCapability.EXECUTE); } /** Getter for repository path. It is immutable reflecting * NbClassPath.createLibraryPath (). Is here only to be displayed in property sheet. */ public NbClassPath getLibraryPath () { return NbClassPath.createLibraryPath (); } /** Get environment variables. * @return the <code><i>NAME</i>=<i>VALUE</i></code> pairs, or <code>null</code> (typically, inherit that of parent) */ public String[] getEnvironmentVariables () { return envp; } /** Set environment variables. * @param nue the new variables * @see #getEnvironmentVariables */ public synchronized void setEnvironmentVariables (String[] nue) { String[] old = envp; envp = nue; firePropertyChange ("environmentVariables", old, nue); // NOI18N } /** Get the working directory. * Note that using a nondefault working directory will only work on JDK 1.3. * @return the working directory to use, or <code>null</code> (use that of parent) */ public File getWorkingDirectory () { return cwd; } /** Set the working directory. * @param nue the new directory * @see #getWorkingDirectory */ public synchronized void setWorkingDirectory (File nue) { File old = cwd; cwd = nue; firePropertyChange ("workingDirectory", old, nue); // NOI18N } /** Called to create the java.lang.Process for given exec info. * Current implementation scans creates new Format with provided * exec info and asks the current executor to start with that * format. * <P> * Subclasses can override this to achive the right behaviour, add * system properties, own format, etc. * * @param info exec info * @return the executed process * @exception IOException if the action fails */ protected Process createProcess (ExecInfo info) throws IOException { return getExternalExecutor ().exec (new Format ( info, getClassPath (), getBootClassPath (), getRepositoryPath (), getLibraryPath () ), envp, cwd); } /* Executes given class by creating new process in underlting operating system. * @param ctx used to write to the Output Window * @param info information about the class to be executed */ public ExecutorTask execute(ExecInfo info) throws IOException { PERunnable run = new PERunnable(info); synchronized (run) { InputOutput inout = (needsIO() ? null : InputOutput.NULL); ExecutorTask et = TopManager.getDefault().getExecutionEngine().execute(info.getClassName(), run, inout); run.setExecutorTask(et); try { run.wait(); Throwable t = run.getException(); if (t != null) { if (t instanceof RuntimeException) { throw (RuntimeException) t; } else if (t instanceof Error) { throw (Error) t; } else if (t instanceof IOException) { throw (IOException) t; } else { throw new FoldingIOException(t); } } return run.getExecutorTask(); } catch (InterruptedException e) { throw new FoldingIOException(e); } } } private class PERunnable implements Runnable { private ExecInfo info; private ExecutorTask fromEngine; ExecutorTask fromMe; private Throwable t; PERunnable(ExecInfo info) { this.info = info; } public synchronized void run() { try { String className = info.getClassName (); Process process = createProcess (info); Thread[] copyMakers = new Thread[3]; (copyMakers[0] = new CopyMaker(fromEngine.getInputOutput().getIn(), new OutputStreamWriter(process.getOutputStream()), true, className)).start(); (copyMakers[1] = new CopyMaker(new InputStreamReader(process.getInputStream()), fromEngine.getInputOutput().getOut(), false, className)).start(); (copyMakers[2] = new CopyMaker(new InputStreamReader(process.getErrorStream()), fromEngine.getInputOutput().getErr(), false, className)).start(); fromMe = new ExternalExecutorTask(this, fromEngine, process, copyMakers); } catch (ThreadDeath tt) { throw tt; } catch (Throwable tt) { t = tt; } finally { notifyAll(); } } public void setExecutorTask(ExecutorTask fromEngine) { this.fromEngine = fromEngine; TaskListener tl = new TaskListener() { public void taskFinished(Task t) { if (fromMe != null) { fromMe.stop(); } } }; fromEngine.addTaskListener(tl); } public ExecutorTask getExecutorTask() { return fromMe; } public Throwable getException() { return t; } } /** Default format that can format tags related to execution. These include settings of classpath * (can be composed from repository, class path, boot class path and libraries), putting somewhere * the name of executed class and its arguments. */ public static class Format extends MapFormat { /** Tag replaced with ProcessExecutors.getClassPath () */ public static final String TAG_CLASSPATH = "classpath"; // NOI18N /** Tag replaced with ProcessExecutors.getBootClassPath () */ public static final String TAG_BOOTCLASSPATH = "bootclasspath"; // NOI18N /** Tag replaced with ProcessExecutors.getRepositoryPath () */ public static final String TAG_REPOSITORY = "filesystems"; // NOI18N /** Tag replaced with ProcessExecutors.getLibraryPath () */ public static final String TAG_LIBRARY = "library"; // NOI18N /** Tag replaced with name of executed class */ public static final String TAG_CLASSNAME = "classname"; // NOI18N /** Tag replaced with arguments of the program */ public static final String TAG_ARGUMENTS = "arguments"; // NOI18N /** Tag replaced with install directory of JDK */ public static final String TAG_JAVAHOME = "java.home"; // NOI18N /** Tag replaced with separator between filename components */ public static final String TAG_SEPARATOR = "/"; // NOI18N /** Tag replaced with separator between path components */ public static final String TAG_PATHSEPARATOR = ":"; // NOI18N static final long serialVersionUID =1105067849363827986L; /** All values for the paths takes from NbClassPath.createXXX methods. * @param info exec info about class to execute */ public Format (ExecInfo info) { this ( info, NbClassPath.createClassPath (), NbClassPath.createBootClassPath (), NbClassPath.createRepositoryPath (FileSystemCapability.EXECUTE), NbClassPath.createLibraryPath () ); } /** @param info exec info about class to execute * @param classPath to substitute instead of CLASSPATH * @param bootClassPath boot class path * @param repository repository path * @param library library path */ public Format ( ExecInfo info, NbClassPath classPath, NbClassPath bootClassPath, NbClassPath repository, NbClassPath library ) { super (new java.util.HashMap (7)); java.util.Map map = getMap (); map.put (TAG_CLASSPATH, classPath.getClassPath ()); map.put (TAG_BOOTCLASSPATH, bootClassPath.getClassPath ()); map.put (TAG_REPOSITORY, repository.getClassPath ()); map.put (TAG_LIBRARY, library.getClassPath ()); map.put (TAG_CLASSNAME, info.getClassName ()); map.put (TAG_JAVAHOME, System.getProperty("java.home")); map.put (TAG_SEPARATOR, java.io.File.separator); map.put (TAG_PATHSEPARATOR, java.io.File.pathSeparator); // JST: // it is not too nice that we have to create string from string[] // and the string will be later parsed again, but hopefully it // will work StringBuffer sb = new StringBuffer (); String[] args = info.getArguments (); for (int i = 0; i < args.length; i++) { sb.append ('\"'); sb.append (args[i]); sb.append ('\"'); sb.append (' '); } map.put (TAG_ARGUMENTS, sb.toString ()); } } /** This thread simply reads from given Reader and writes read chars to given Writer. */ private static class CopyMaker extends Thread { final Writer os; final Reader is; /** while set to false at streams that writes to the OutputWindow it must be * true for a stream that reads from the window. */ final boolean autoflush; final String permName; CopyMaker(Reader is, Writer os, boolean b, String className) { this.os = os; this.is = is; autoflush = b; permName = className; } /* Makes copy. */ public void run() { int read; try { while ((read = is.read()) >= 0x0) { os.write(read); if (autoflush) os.flush(); } } catch (IOException ex) { } } } // end of CopyMaker /** SysProcess that describes the external process. */ static class ExternalExecutorTask extends ExecutorTask { Process proc; Thread[] copyMakers; ExecutorTask foreign; ExternalExecutorTask(Runnable run, ExecutorTask etask, Process proc, Thread[] copyMakers) { super(run); this.proc = proc; this.copyMakers = copyMakers; foreign = etask; TaskListener tl = new TaskListener() { public void taskFinished(Task t) { stop(); } }; etask.addTaskListener(tl); new Thread() { public void run() { result(); } }.start(); } public void stop() { copyMakers[0].interrupt(); copyMakers[1].interrupt(); copyMakers[2].interrupt(); proc.destroy(); } public int result() { try { int ret = proc.waitFor(); Thread.sleep(2000); // time for copymakers stop(); return ret; } catch (InterruptedException e) { return 1; // 0 is success } finally { notifyFinished(); } } public InputOutput getInputOutput() { return foreign.getInputOutput(); } public void run() { } } // end of ExternalProcess } /* * Log * 48 Gandalf-post-FCS1.46.2.0 4/5/00 Ales Novak Readers used instead of * InputStreams * 47 Gandalf 1.46 1/13/00 Ian Formanek NOI18N * 46 Gandalf 1.45 1/12/00 Ian Formanek NOI18N * 45 Gandalf 1.44 12/27/99 Ian Formanek Repositor2Filesystems * change * 44 Gandalf 1.43 12/21/99 Jesse Glick External executors can * set envvars and (on 1.3) cwd. * 43 Gandalf 1.42 11/24/99 Ales Novak thread that waits for * the end of an external process moved from ExecSupport here * 42 Gandalf 1.41 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 41 Gandalf 1.40 10/8/99 Ales Novak killing of tasks * improved * 40 Gandalf 1.39 10/1/99 Jesse Glick Cleanup of service type * name presentation. * 39 Gandalf 1.38 10/1/99 Ales Novak major change of * execution * 38 Gandalf 1.37 9/29/99 Ales Novak FilesystemCapability.EXECUTION * used * 37 Gandalf 1.36 9/10/99 Petr Jiricka handleExecute now throws * IOException * 36 Gandalf 1.35 9/10/99 Jaroslav Tulach Services changes. * 35 Gandalf 1.34 8/17/99 Ian Formanek Generated serial version * UID * 34 Gandalf 1.33 8/8/99 Ian Formanek Hints for executor * arguments * 33 Gandalf 1.32 7/19/99 Martin Ryzl tags for process name * added * 32 Gandalf 1.31 7/2/99 Jesse Glick Help IDs for debugger & * executor types. * 31 Gandalf 1.30 6/11/99 Ian Formanek Fixed creation of * cmd-line for external executor (part of build 339) * 30 Gandalf 1.29 6/11/99 Ales Novak library added * 29 Gandalf 1.28 6/8/99 Ian Formanek ---- Package Change To * org.openide ---- * 28 Gandalf 1.27 6/8/99 Ales Novak FSCapability.EXECUTE * used * 27 Gandalf 1.26 6/7/99 Jaroslav Tulach FS capabilities. * 26 Gandalf 1.25 6/3/99 Jaroslav Tulach Executors are serialized * in project. * 25 Gandalf 1.24 6/2/99 Petr Jiricka Fixed bug - initializing * the name in the constructor * 24 Gandalf 1.23 6/1/99 Jaroslav Tulach * 23 Gandalf 1.22 6/1/99 Jaroslav Tulach Allows subclasses forbid * change of name when executor is changed. * 22 Gandalf 1.21 5/31/99 Jaroslav Tulach External Execution & * Compilation * 21 Gandalf 1.20 5/27/99 Jesse Glick [JavaDoc] * 20 Gandalf 1.19 5/27/99 Jaroslav Tulach Executors rearanged. * 19 Gandalf 1.18 5/25/99 Petr Jiricka Default external * classpath now includes SYSTEM * 18 Gandalf 1.17 5/18/99 Petr Hamernik frixed bug #1638 once * more * 17 Gandalf 1.16 5/17/99 Petr Hamernik fixed bug #1638 * 16 Gandalf 1.15 4/21/99 Ales Novak commandline parsing + * no_classpath * 15 Gandalf 1.14 4/9/99 Ales Novak fix for newlines * 14 Gandalf 1.13 4/7/99 Ales Novak * 13 Gandalf 1.12 3/31/99 Jesse Glick [JavaDoc] * 12 Gandalf 1.11 3/31/99 Ales Novak * 11 Gandalf 1.10 3/24/99 Ales Novak * 10 Gandalf 1.9 3/23/99 Jesse Glick [JavaDoc] * 9 Gandalf 1.8 3/19/99 Jaroslav Tulach TopManager.getDefault * ().getRegistry () * 8 Gandalf 1.7 3/4/99 Ales Novak * 7 Gandalf 1.6 2/11/99 Ian Formanek Renamed FileSystemPool * -> Repository * 6 Gandalf 1.5 2/10/99 Ales Novak * 5 Gandalf 1.4 2/1/99 Ian Formanek CLASSPATH changes for * JDK 1.2 * 4 Gandalf 1.3 1/15/99 Ales Novak * 3 Gandalf 1.2 1/13/99 Ales Novak * 2 Gandalf 1.1 1/6/99 Jaroslav Tulach Change of package of * DataObject * 1 Gandalf 1.0 1/5/99 Ian Formanek * $ */